#!/usr/bin/env python
"""
系统性能监控脚本 - 收集Django应用系统性能指标并保存
"""

import os
import sys
import time
import psutil
import json
import argparse
import datetime
import socket
import requests
import logging
from pathlib import Path
import django

# 添加项目根目录到Python路径
BASE_DIR = Path(__file__).resolve().parent.parent
sys.path.append(str(BASE_DIR))

# 设置Django环境
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'boxuser.settings')
django.setup()

# 导入Django设置和模型
from django.conf import settings
from django.db import connection, OperationalError

# 设置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler(os.path.join(BASE_DIR, 'logs', 'system_metrics.log')),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger('system_metrics')

# 确保日志目录存在
os.makedirs(os.path.join(BASE_DIR, 'logs'), exist_ok=True)
os.makedirs(os.path.join(BASE_DIR, 'metrics'), exist_ok=True)

class SystemMetrics:
    """系统性能指标收集类"""
    
    def __init__(self, app_url=None, interval=60, output_file=None):
        """初始化监控参数"""
        self.app_url = app_url
        if app_url is None:
            host = socket.gethostbyname(socket.gethostname())
            port = getattr(settings, 'PORT', 8000)
            self.app_url = f"http://{host}:{port}/"
        
        self.interval = interval
        self.process = None
        self.pid_file = os.path.join(BASE_DIR, 'metrics', 'app_process.pid')
        
        if output_file:
            self.output_file = output_file
        else:
            timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
            self.output_file = os.path.join(BASE_DIR, 'metrics', f'system_metrics_{timestamp}.json')
    
    def find_django_process(self):
        """查找Django应用进程"""
        # 先尝试从PID文件读取
        if os.path.exists(self.pid_file):
            try:
                with open(self.pid_file, 'r') as f:
                    pid = int(f.read().strip())
                if psutil.pid_exists(pid):
                    process = psutil.Process(pid)
                    cmdline = ' '.join(process.cmdline())
                    if 'python' in cmdline and ('manage.py runserver' in cmdline or 'gunicorn' in cmdline):
                        logger.info(f"找到Django进程，PID: {pid}")
                        return process
            except (ValueError, psutil.NoSuchProcess):
                pass
        
        # 如果PID文件不存在或无效，则搜索进程
        for process in psutil.process_iter(['pid', 'name', 'cmdline']):
            try:
                cmdline = ' '.join(process.cmdline())
                if 'python' in cmdline and ('manage.py runserver' in cmdline or 'gunicorn' in cmdline):
                    logger.info(f"找到Django进程，PID: {process.pid}")
                    # 保存PID供将来使用
                    with open(self.pid_file, 'w') as f:
                        f.write(str(process.pid))
                    return process
            except (psutil.NoSuchProcess, psutil.AccessDenied):
                continue
        
        logger.warning("未能找到Django应用进程")
        return None
    
    def collect_system_metrics(self):
        """收集系统级别的性能指标"""
        metrics = {
            'timestamp': datetime.datetime.now().isoformat(),
            'system': {
                'cpu': {
                    'percent': psutil.cpu_percent(interval=1, percpu=True),
                    'count': psutil.cpu_count(),
                    'avg_load': psutil.cpu_percent(interval=1)
                },
                'memory': {
                    'total': psutil.virtual_memory().total,
                    'available': psutil.virtual_memory().available,
                    'used': psutil.virtual_memory().used,
                    'percent': psutil.virtual_memory().percent
                },
                'disk': {
                    'total': psutil.disk_usage('/').total,
                    'used': psutil.disk_usage('/').used,
                    'free': psutil.disk_usage('/').free,
                    'percent': psutil.disk_usage('/').percent
                },
                'network': self.get_network_info()
            }
        }
        
        # 如果找到Django进程，添加进程级别的指标
        if self.process:
            try:
                metrics['process'] = {
                    'pid': self.process.pid,
                    'cpu_percent': self.process.cpu_percent(interval=1),
                    'memory_percent': self.process.memory_percent(),
                    'memory_info': {
                        'rss': self.process.memory_info().rss,  # 物理内存
                        'vms': self.process.memory_info().vms,  # 虚拟内存
                    },
                    'threads_count': self.process.num_threads(),
                    'open_files': len(self.process.open_files()),
                    'connections': len(self.process.connections())
                }
            except (psutil.NoSuchProcess, psutil.AccessDenied):
                logger.warning("进程已终止或无权限访问")
                self.process = None
        
        # 添加数据库指标
        metrics['database'] = self.collect_db_metrics()
        
        # 添加应用响应时间
        metrics['application'] = self.check_application_health()
        
        return metrics
    
    def get_network_info(self):
        """获取网络接口信息"""
        net_io = psutil.net_io_counters()
        return {
            'bytes_sent': net_io.bytes_sent,
            'bytes_recv': net_io.bytes_recv,
            'packets_sent': net_io.packets_sent,
            'packets_recv': net_io.packets_recv,
            'errin': net_io.errin,
            'errout': net_io.errout,
            'dropin': net_io.dropin,
            'dropout': net_io.dropout
        }
    
    def collect_db_metrics(self):
        """收集数据库性能指标"""
        db_metrics = {
            'connected': False,
            'queries': []
        }
        
        try:
            with connection.cursor() as cursor:
                # 检查连接
                start_time = time.time()
                cursor.execute("SELECT 1")
                result = cursor.fetchone()
                query_time = time.time() - start_time
                db_metrics['connected'] = (result[0] == 1)
                db_metrics['connection_time'] = query_time
                
                # 查询表数量
                cursor.execute("SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE()")
                db_metrics['tables_count'] = cursor.fetchone()[0]
                
                # 查询活动连接数量
                try:
                    cursor.execute("SHOW STATUS LIKE 'Threads_connected'")
                    result = cursor.fetchone()
                    if result:
                        db_metrics['active_connections'] = int(result[1])
                except:
                    pass
                
        except OperationalError as e:
            logger.error(f"数据库连接失败: {e}")
            db_metrics['error'] = str(e)
        
        return db_metrics
    
    def check_application_health(self):
        """检查应用响应时间"""
        app_metrics = {
            'status': 'unknown',
            'response_time': None
        }
        
        try:
            start_time = time.time()
            response = requests.get(self.app_url, timeout=5)
            response_time = time.time() - start_time
            
            app_metrics['status'] = 'ok' if response.status_code == 200 else 'error'
            app_metrics['status_code'] = response.status_code
            app_metrics['response_time'] = response_time
        except requests.RequestException as e:
            app_metrics['status'] = 'error'
            app_metrics['error'] = str(e)
        
        return app_metrics
    
    def save_metrics(self, metrics):
        """将指标保存到文件"""
        try:
            # 如果文件已存在，则追加数据
            if os.path.exists(self.output_file):
                with open(self.output_file, 'r') as f:
                    data = json.load(f)
                data.append(metrics)
            else:
                data = [metrics]
            
            with open(self.output_file, 'w') as f:
                json.dump(data, f, indent=2)
            
            logger.info(f"指标数据已保存到: {self.output_file}")
            return True
        except Exception as e:
            logger.error(f"保存指标数据失败: {e}")
            return False
    
    def monitor(self, duration=None):
        """持续监控系统性能"""
        self.process = self.find_django_process()
        
        logger.info(f"开始监控系统指标，保存至: {self.output_file}")
        logger.info(f"应用URL: {self.app_url}")
        logger.info(f"采集间隔: {self.interval}秒")
        
        if duration:
            logger.info(f"监控持续时间: {duration}秒")
            end_time = time.time() + duration
        else:
            logger.info("持续监控直到手动停止")
            end_time = None
        
        count = 0
        try:
            while True:
                metrics = self.collect_system_metrics()
                self.save_metrics(metrics)
                count += 1
                
                # 定时打印简要统计
                if count % 10 == 0:
                    logger.info(f"已收集 {count} 次指标数据")
                    if metrics['application']['status'] == 'ok':
                        logger.info(f"应用响应时间: {metrics['application']['response_time']:.4f}秒")
                    else:
                        logger.warning(f"应用响应异常: {metrics['application'].get('error', '未知错误')}")
                
                # 检查是否达到监控时间
                if end_time and time.time() >= end_time:
                    break
                
                time.sleep(self.interval)
        except KeyboardInterrupt:
            logger.info("监控被用户中断")
        finally:
            logger.info(f"完成监控，共收集 {count} 次指标数据")

def main():
    parser = argparse.ArgumentParser(description='Django应用系统性能监控工具')
    parser.add_argument('--url', help='应用URL地址')
    parser.add_argument('--interval', type=int, default=60, help='采集间隔（秒）')
    parser.add_argument('--duration', type=int, help='监控持续时间（秒），不提供则持续监控')
    parser.add_argument('--output', help='输出文件路径')
    
    args = parser.parse_args()
    
    monitor = SystemMetrics(
        app_url=args.url,
        interval=args.interval,
        output_file=args.output
    )
    
    monitor.monitor(duration=args.duration)

if __name__ == "__main__":
    main() 